﻿// -----------------------------------------------------------------------
// <copyright file="Program.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentStatement.Sample
{
    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using System.Reflection;

    /// <summary>
    /// 
    /// </summary>
    public class Program
    {
        /// <summary>
        /// 
        /// </summary>
        protected static readonly PropertyInfo DebugView = typeof(Expression).GetProperty("DebugView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        public static void Main(string[] args)
        {
            var list = new List<int>() { 1, 2, 3, 4, 5 };

            /*
                // Normally you would do:
                Expression<Action> exp = () => Statement.Start(s => s.Empty());
                exp = exp.Expand();

                // Or
                exp = exp.Expand(typeof(MyFluentStatementVisitor));

                Expression<Func<int>> exp2 = () => Statement.Start(s => s.Value<int>(s.Return(1)));

                exp2 = exp2.Expand();

                // Or
                exp2 = exp2.Expand(typeof(MyFluentStatementVisitor));
            */

            Expression<Action> printHello1 = () => Statement.Start(s => s.ConsoleWriteLine("Hello1").ConsoleWriteLine("Hello2"));
            printHello1 = printHello1.Expand(typeof(MyVisitor));

            Expression<Action<int>> printHello2 = p => Statement.Start(s => s.ConsoleWriteLine(string.Format("Hello {0}", p)));
            printHello2 = printHello2.Expand(typeof(MyVisitor));

            Expression<Func<int, string>> printHello3 = p => Statement.Start<string>(s => s.Value<string>(s.Return(string.Format("Hello {0}", p))));
            printHello3 = printHello3.Expand();

            // Invoke
            Run<Action>(() => Statement.Start(s => s.Invoke(printHello1)));
            Run<Action>(() => Statement.Start(s => s.Invoke(printHello2, 1)));
            Run<Action>(() => Statement.Start(s => s.ConsoleWriteLine(s.Value<string>(s.Invoke<string>(printHello3, 1)))));

            // Example of extension method
            Run<Action>(() => Statement.Start(s => s.ConsoleWriteLine("Hello")));

            // Empty
            Run<Action>(() => Statement.Start(s => s.Empty()));

            // Do
            Run<Action>(() => Statement.Start(s => s.Do(() => Console.WriteLine("Hello"))));

            // Do nothing
            Run<Action>(() => Statement.Start(s => s.Do(null)));

            // Return (void)
            Run<Action>(() => Statement.Start(s => s.Return()));

            // Return and Value/DefaultValue
            Run<Action>(() => Statement.Start(s => s.Value<int>(s.Return(Math.Abs(10)))));
            Run<Action>(() => Statement.Start(s => s.Value<int>(s.Return(s.DefaultValue<int>()))));

            // Block(s)
            Run<Action>(() => Statement.Start(s => s.Block(5, p => s.Do(() => Console.WriteLine("Hello {0}", p)))));
            Run<Action>(() => Statement.Start(s => s.Block(5, p => s.PostIncrement(p).Do(() => Console.WriteLine("Hello {0}", p)))));
            Run<Action>(() => Statement.Start(s => s.Block(new List<int>(), p => s.Do(() => p.Add(1)).Do(() => Console.WriteLine("Hello {0}", p.Count)))));
            Run<Action>(() => Statement.Start(s => s.Block(new Random().Next(), p => s.Do(() => Console.WriteLine("Hello {0}", p)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = new Random().Next() }, p => s.Do(() => Console.WriteLine("Hello {0}", p.A)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, null)));
            Run<Action>(() => Statement.Start(s => s.Block((object)null, p => s.Do(() => Console.WriteLine("Hello")))));
            Run<Action>(() => Statement.Start(s => s.Block((object)null, null)));

            // Assign
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, p => s.Assign(p.A, p.A + 1).Do(() => Console.WriteLine("Hello {0}", p.A)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, p => s.Assign(p.A, 5).Do(() => Console.WriteLine("Hello {0}", p.A)))));

            // Pre/Post Increment/Decrement
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, p => s.PreIncrement(p.A).Do(() => Console.WriteLine("Hello {0}", p.A)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, p => s.PreDecrement(p.A).Do(() => Console.WriteLine("Hello {0}", p.A)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, p => s.PostIncrement(p.A).Do(() => Console.WriteLine("Hello {0}", p.A)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, p => s.PostDecrement(p.A).Do(() => Console.WriteLine("Hello {0}", p.A)))));

            // Increase/Decrease
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, p => s.Increase(p.A, 2).Do(() => Console.WriteLine("Hello {0}", p.A)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 1, B = 2 }, p => s.Decrease(p.A, 2).Do(() => Console.WriteLine("Hello {0}", p.A)))));

            // Goto/Label
            Run<Action>(() => Statement.Start(s => s.Goto("Label2").Label("Label1").Do(() => Console.WriteLine("Label1")).Block(s.Return()).Label("Label2").Do(() => Console.WriteLine("Label2")).Goto("Label1")));

            // While
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 10, s.Do(() => Console.WriteLine("Hello {0}", p.A)).Assign(p.A, p.A + 1).If(p.A == 5, s.Do(() => Console.WriteLine("We are at 5")).Continue(), null)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 10, s.Do(() => Console.WriteLine("Hello {0}", p.A)).Assign(p.A, p.A + 1).If(p.A == 5, s.Break(), null)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 10, s.Do(() => Console.WriteLine("Hello {0}", p.A)).Assign(p.A, p.A + 1).If(p.A == 5, s.Break())))));
            Run<Action>(() => Statement.Start(s => s.While(false, null)));

            // DoWhile
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.DoWhile(s.Do(() => Console.WriteLine("Hello {0}", p.A)).Assign(p.A, p.A + 1).If(p.A == 5, s.Break(), null), p.A < 10))));
            Run<Action>(() => Statement.Start(s => s.DoWhile(null, false)));

            // DoUntil
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.DoUntil(s.Do(() => Console.WriteLine("Hello {0}", p.A)).Assign(p.A, p.A + 1).If(p.A == 5, s.Break(), null), p.A >= 10))));
            Run<Action>(() => Statement.Start(s => s.DoUntil(null, true)));

            // For
            Run<Action>(() => Statement.Start(s => s.For(0, p => p < 10, p => s.Assign(p, p + 1), p => s.Do(() => Console.WriteLine("Hello {0}", p)))));
            Run<Action>(() => Statement.Start(s => s.For(new { A = 0 }, p => p.A < 10, p => s.Assign(p.A, p.A + 1), p => s.Do(() => Console.WriteLine("Hello {0}", p.A)))));

            // ForEach
            Run<Action>(() => Statement.Start(s => s.ForEach(list, p => s.Do(() => Console.WriteLine("Hello {0}", p)))));
            Run<Action>(() => Statement.Start(s => s.ForEach(new List<int>() { 1, 2, 3, 4, 5 }, p => s.Do(() => Console.WriteLine("Hello {0}", p)))));

            // If
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 3, s.If(p.A <= 1, s.Do(() => Console.WriteLine("Hello 0-1"))).Assign(p.A, p.A + 1)))));

            // IfElse
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 3, s.If(p.A <= 1, s.Do(() => Console.WriteLine("Hello 0-1")), s.Do(() => Console.WriteLine("Hello other"))).Assign(p.A, p.A + 1)))));

            // IfElseIf
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 3, s.If(s.Do(() => Console.WriteLine("Hello other")), Tuple.Create<bool, Statement>(p.A == 0, s.Do(() => Console.WriteLine("Hello 0"))), new Tuple<bool, Statement>(p.A == 1, s.Do(() => Console.WriteLine("Hello 1")))).Assign(p.A, p.A + 1)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 3, s.If(s.Do(() => Console.WriteLine("Hello other")), s.MakeIf(p.A == 0, s.Do(() => Console.WriteLine("Hello 0"))), s.MakeIf(p.A == 1, s.Do(() => Console.WriteLine("Hello 1")))).Assign(p.A, p.A + 1)))));

            // Switch
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 4, s.Switch(p.A, s.Do(() => Console.WriteLine("Hello other")), Tuple.Create<int[], Statement>(new[] { 0, 1 }, s.Do(() => Console.WriteLine("Hello 0-1"))), Tuple.Create<int[], Statement>(new[] { 2 }, s.Do(() => Console.WriteLine("Hello 2")))).Assign(p.A, p.A + 1)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 4, s.Switch(p.A, Tuple.Create<int[], Statement>(new[] { 0, 1 }, s.Do(() => Console.WriteLine("Hello 0-1"))), Tuple.Create<int[], Statement>(new[] { 2 }, s.Do(() => Console.WriteLine("Hello 2")))).Assign(p.A, p.A + 1)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = 0 }, p => s.While(p.A < 4, s.Switch(p.A, s.MakeCase(new[] { 0, 1 }, s.Do(() => Console.WriteLine("Hello 0-1"))), s.MakeCase(2, s.Do(() => Console.WriteLine("Hello 2")))).Assign(p.A, p.A + 1)))));

            // Lock
            Run<Action>(() => Statement.Start(s => s.Block(new { A = new object() }, p => s.Lock(p.A, s.Do(() => Console.WriteLine("Hello"))))));
            Run<Action>(() => Statement.Start(s => s.Lock(new object(), s.Do(() => Console.WriteLine("Hello")))));

            // TryLock
            Run<Action>(() => Statement.Start(s => s.TryLock(new object(), TimeSpan.FromTicks(-1), lockTacken => s.Do(() => Console.WriteLine("Hello {0}", lockTacken)))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = new object() }, p => s.TryLock(p.A, TimeSpan.FromTicks(-1), lockTacken => s.Do(() => Console.WriteLine("Hello {0}", lockTacken))))));

            // Using
            Run<Action>(() => Statement.Start(s => s.Using(new System.IO.MemoryStream(), p => s.Do(() => Console.WriteLine("Hello {0}", p.Length)))));
            Run<Action>(() => Statement.Start(s => s.Using((IDisposable)null, p => s.Do(() => Console.WriteLine("Hello {0}", p)))));

            // TryFinally
            Run<Action>(() => Statement.Start(s => s.TryFinally(s.Do(() => Console.WriteLine("Hello")), s.Do(() => Console.WriteLine("Final hello")))));

            // TryCatch/Throw
            Run<Action>(() => Statement.Start(s => s.TryCatch(s.Throw(new ArgumentException("Hello")), Tuple.Create<Type, Func<Exception, Statement>>(typeof(ArgumentException), e => s.Do(() => Console.WriteLine("Exception AE: {0}, {1}", e.GetType(), e.Message))), Tuple.Create<Type, Func<Exception, Statement>>(typeof(OverflowException), e => s.Do(() => Console.WriteLine("Exception OF: {0}, {1}", e.GetType(), e.Message))), Tuple.Create<Type, Func<Exception, Statement>>(typeof(Exception), e => s.Do(() => Console.WriteLine("Exception: {0}, {1}", e.GetType(), e.Message))))));
            Run<Action>(() => Statement.Start(s => s.TryCatch(s.Throw(new OverflowException("Hello")), Tuple.Create<Type, Func<Exception, Statement>>(typeof(ArgumentException), e => s.Do(() => Console.WriteLine("Exception AE: {0}, {1}", e.GetType(), e.Message))), Tuple.Create<Type, Func<Exception, Statement>>(typeof(OverflowException), e => s.Do(() => Console.WriteLine("Exception OF: {0}, {1}", e.GetType(), e.Message))))));
            Run<Action>(() => Statement.Start(s => s.TryCatch(s.Throw(new OverflowException("Hello")), Tuple.Create<Type, Func<Exception, Statement>>(typeof(ArgumentException), null), Tuple.Create<Type, Func<Exception, Statement>>(typeof(Exception), null))));
            Run<Action>(() => Statement.Start(s => s.TryCatch(s.Throw(new OverflowException("Hello")), new Tuple<Type, Func<Exception, Statement>>(typeof(ArgumentException), null), new Tuple<Type, Func<Exception, Statement>>(typeof(Exception), null))));
            Run<Action>(() => Statement.Start(s => s.TryCatch(s.Throw(new OverflowException("Hello")), s.MakeCatch(typeof(ArgumentNullException), (ArgumentNullException e) => s.Do(() => Console.WriteLine("Exception AN: {0}, {1}", e.GetType(), e.Message))), s.MakeCatch(typeof(ArgumentException), null), s.MakeCatch(typeof(OverflowException), (OverflowException e) => s.Do(() => Console.WriteLine("Exception OF: {0}, {1}", e.GetType(), e.Message))))));
            Run<Action>(() => Statement.Start(s => s.TryCatch(s.Throw(new OverflowException("Hello")), s.MakeCatch(typeof(OverflowException), null))));

            // Equivalent to try {} catch {} without type (if you look at the IL code you'll see it's a catch System.Object)
            Run<Action>(() => Statement.Start(s => s.TryCatch(s.Throw(new OverflowException("Hello")), s.MakeCatch(typeof(object), (object e) => s.Do(() => Console.WriteLine("Exception Other: {0}", e))))));

            // TryCatchFinally/Throw
            Run<Action>(() => Statement.Start(s => s.TryCatchFinally(s.Throw(new ArgumentException("Hello")), s.Do(() => Console.WriteLine("Finally")), Tuple.Create<Type, Func<Exception, Statement>>(typeof(ArgumentException), e => s.Do(() => Console.WriteLine("Exception AE: {0}, {1}", e.GetType(), e.Message))), Tuple.Create<Type, Func<Exception, Statement>>(typeof(OverflowException), e => s.Do(() => Console.WriteLine("Exception OF: {0}, {1}", e.GetType(), e.Message))), Tuple.Create<Type, Func<Exception, Statement>>(typeof(Exception), e => s.Do(() => Console.WriteLine("Exception: {0}, {1}", e.GetType(), e.Message))))));

            // Ugly examples of use of out parameter (the same for ref)
            Run<Action>(() => Statement.Start(s => s.Block(new { A = new int[1] }, p => s.Do(() => int.TryParse("1234", out p.A[0])).Do(() => Console.WriteLine(p.A[0])))));
            Run<Action>(() => Statement.Start(s => s.Block(new { A = new int[1], B = false }, p => s.Assign(p.B, int.TryParse("1234", out p.A[0])).Do(() => Console.WriteLine("{0}: {1}", p.B, p.A[0])))));

            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TDelegate"></typeparam>
        /// <param name="expression"></param>
        protected static void Run<TDelegate>(Expression<TDelegate> expression)
        {
            var expression2 = expression.Expand(typeof(MyVisitor));
            TDelegate method = expression2.Compile();
            Delegate del = method as Delegate;

            Console.WriteLine();
            Console.WriteLine("---");
            Console.WriteLine();
            Console.WriteLine(DebugView.GetValue(expression2, null));
            Console.WriteLine();
            Console.WriteLine(del.DynamicInvoke());
        }
    }
}